﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Lynn.Infastructure.Utils;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class AppServiceHelper
    {
        static readonly List<TypeInfo> typeInfos;
        static AppServiceHelper()
        {
            typeInfos = AssemblyHelper.FindTypeInfos(x => x.IsClass || x.IsInterface).Where(x=>x.CustomAttributes.Count(x => x.AttributeType == typeof(AppIgnoreAttribute))==0).ToList();
        }
        /// <summary>
        /// 根据条件自动注入
        /// </summary>
        /// <param name="services"></param>
        /// <param name="where"></param>
        /// <param name="autoFacTargets"></param>
        /// <returns></returns>
        public static IServiceCollection AddAppService(this IServiceCollection services, Func<TypeInfo, bool> where = null, AppServiceTargets autoFacTargets = AppServiceTargets.Scoped)
        {
            var classes = typeInfos;
            if (where != null)
            {
                classes = classes.Where(where).ToList();
            }
            Route(ref services, classes, autoFacTargets);
            return services;
        }
        /// <summary>
        /// 根据特性自动注入
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddAppServiceByAttribute(this IServiceCollection services)
        {
            var classes = typeInfos.Where(x => x.GetCustomAttribute(typeof(AppServiceAttribute)) != null).ToList();
            foreach (var type in classes)
            {
                var attr = (AppServiceAttribute)type.GetCustomAttribute(typeof(AppServiceAttribute));
                Route(ref services, type, attr.autoFacTargets);
            }
            return services;
        }
        /// <summary>
        /// 识别构造参数类型是types的类并全部注入 多参数的构造函数
        /// </summary>
        /// <param name="services"></param>
        /// <param name="types">构造函数参数类型 按顺序</param>
        /// <param name="autoFacTargets"></param>
        /// <returns></returns>
        public static IServiceCollection AddAppService(this IServiceCollection services, Type[] types, AppServiceTargets autoFacTargets = AppServiceTargets.Scoped)
        {
            var classes = typeInfos.Where(x => x.GetConstructor(types) != null).ToList();
            Route(ref services, classes, autoFacTargets);
            return services;
        }
        /// <summary>
        /// 识别构造参数类型是TContext的类并全部注入 单参数的构造函数
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="autoFacTargets"></param>
        /// <returns></returns>
        public static IServiceCollection AddAppService<TContext>(this IServiceCollection services, AppServiceTargets autoFacTargets = AppServiceTargets.Scoped) where TContext:class
        {
            var classes = typeInfos.Where(x => x.GetConstructor(new Type[] { typeof(TContext) }) != null).ToList();
            Route(ref services, classes, autoFacTargets);
            return services;
        }
        /// <summary>
        /// 区分接口和类，如果类实现了某些接口，则将所有接口注入
        /// </summary>
        /// <param name="services"></param>
        /// <param name="typeInfos"></param>
        /// <param name="autoFacTargets"></param>
        private static void Route(ref IServiceCollection services, List<TypeInfo> typeInfos, AppServiceTargets autoFacTargets = AppServiceTargets.Scoped)
        {
            foreach (var type in typeInfos)
            {
                Route(ref services, type, autoFacTargets);
            }
        }
        private static void Route(ref IServiceCollection services, TypeInfo type, AppServiceTargets autoFacTargets = AppServiceTargets.Scoped)
        {
            //坑点：ImplementedInterfaces获取到的接口类型无法注入，因此要根据MetadataToken和Name来定位接口类
            if (type.IsClass && type.ImplementedInterfaces.Count() > 0)
            {
                var dic = type.ImplementedInterfaces.ToDictionary(x => x.MetadataToken, x => x.Name);
                var interfaces = typeInfos.Where(x => x.IsInterface).ToList();
                var list = new List<TypeInfo>();
                foreach (var item in dic) {
                    //MetadataToken在同一个程序集相同，多加Name比对减少所有程序集有相同token和Name概率
                    var inter = interfaces.Where(x => x.MetadataToken == item.Key && x.Name == item.Value).FirstOrDefault();
                    if (inter == null) continue;
                    AddServices(ref services, autoFacTargets, inter.GetTypeInfo());
                }
                return;
            }
            AddServices(ref services, autoFacTargets, type.GetTypeInfo());
        }
        private static void AddServices(ref IServiceCollection services, AppServiceTargets autoFacTargets, TypeInfo typeInfo)
        {
            if (typeInfo.CustomAttributes.Count(x => x.AttributeType == typeof(AppIgnoreAttribute)) > 0) return;
            if (typeInfo.IsInterface)
            {
                //查找接口的第一个实现
                var implementationType = typeInfos.Where(x => x.IsClass && x.ImplementedInterfaces.Any(c => c.Name == typeInfo.Name && c.MetadataToken == typeInfo.MetadataToken)).FirstOrDefault();
                if (implementationType == null)
                {
                    return;
                }
                //注入
                switch (autoFacTargets)
                {
                    case AppServiceTargets.Transient: services.TryAddTransient(typeInfo, implementationType); break;
                    case AppServiceTargets.Scoped: services.TryAddScoped(typeInfo, implementationType); break;
                    case AppServiceTargets.Singleton: services.TryAddSingleton(typeInfo, implementationType); break;
                    default: break;
                }
            }
            else
            {
                //注入
                switch (autoFacTargets)
                {
                    case AppServiceTargets.Transient: services.TryAddTransient(typeInfo); break;
                    case AppServiceTargets.Scoped: services.TryAddScoped(typeInfo); break;
                    case AppServiceTargets.Singleton: services.TryAddSingleton(typeInfo); break;
                    default: break;
                }
            }

        }
    }
}
